using System.Globalization;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using CodeSnippet.Formats.Base;

namespace CodeSnippet.Formats
{
    /// <summary>
    /// Generates color-coded HTML 4.01 from HTML/XML/ASPX source code.
    /// </summary>
    /// <remarks>
    /// <para>
    /// This implementation assumes that code inside &lt;script&gt; blocks 
    /// is JavaScript, and code inside &lt;% %&gt; blocks is C#.</para>
    /// <para>
    /// The default tab width is set to 2 characters in this class.</para>
    /// </remarks>
    public class HtmlFormat : SourceFormat
    {
        #region Fields
        private readonly CSharpFormat csf; // To format embedded C# code.
        private readonly JavaScriptFormat jsf; // To format client-side JavaScript code.
        private readonly Regex attribRegex;
        #endregion Fields

        #region Initialization
        public HtmlFormat()
        {
            const string regAttributeMatch = @"(=?"".*?""|=?'.*?')|([\w:-]+)";
            attribRegex = new Regex(regAttributeMatch, RegexOptions.Singleline);
            csf = new CSharpFormat();
            jsf = new JavaScriptFormat();
        }
        /// <summary>
        /// Initializes this instance code regular expression.
        /// </summary>
        protected override Regex ConstructCodeRegex()
        {
            const string regJavaScript = @"(?<=&lt;script(?:\s.*?)?&gt;).+?(?=&lt;/script&gt;)";
            const string regComment = @"&lt;!--.*?--&gt;";
            const string regAspTag = @"&lt;%@.*?%&gt;|&lt;%|%&gt;";
            const string regAspCode = @"(?<=&lt;%).*?(?=%&gt;)";
            const string regTagDelimiter = @"(?:&lt;/?!?\??(?!%)|(?<!%)/?&gt;)+";
            const string regTagName = @"(?<=&lt;/?!?\??(?!%))[\w\.:-]+(?=.*&gt;)";
            const string regAttributes = @"(?<=&lt;(?!%)/?!?\??[\w:-]+).*?(?=(?<!%)/?&gt;)";
            const string regEntity = @"&amp;\w+;";

            // The regex object will handle all the replacements in one pass.
            var regAll = string.Format(CultureInfo.CurrentCulture, "({0})|({1})|({2})|({3})|({4})|({5})|({6})|({7})",
                regJavaScript, regComment, regAspTag, regAspCode, regTagDelimiter,
                regTagName, regAttributes, regEntity);

            return new Regex(regAll, RegexOptions.IgnoreCase | RegexOptions.Singleline);
        }
        #endregion Initialization

        #region Operations
        /// <summary>
        /// Called to evaluate the HTML fragment corresponding to each 
        /// attribute's name/value in the code.
        /// </summary>
        /// <param name="match">The <see cref="Match"/> resulting from a 
        /// single regular expression match.</param>
        /// <returns>A string containing the HTML code fragment.</returns>
        private static string AttributeMatchEval(Match match)
        {
            if (match.Groups[1].Success) //attribute value
            {
                return string.Format(CultureInfo.CurrentCulture, "<span class=\"kwrd\">{0}</span>", match);
            }
            return match.Groups[2].Success ? string.Format(CultureInfo.CurrentCulture, "<span class=\"attr\">{0}</span>", match) : match.ToString();
        }
        /// <summary>
        /// Called to evaluate the HTML fragment corresponding to each 
        /// matching token in the code.
        /// </summary>
        /// <param name="match">The <see cref="Match"/> resulting from a 
        /// single regular expression match.</param>
        /// <returns>A string containing the HTML code fragment.</returns>
        protected override string MatchEval(Match match)
        {
            if (match.Groups[1].Success) //JavaScript code
            {
                return jsf.FormatSubCode(match.ToString());
            }
            if (match.Groups[2].Success) //comment
            {
                var reader = new StringReader(match.ToString());
                string line;
                var sb = new StringBuilder();
                while ((line = reader.ReadLine()) != null)
                {
                    if (sb.Length > 0)
                    {
                        sb.AppendLine();
                    }
                    sb.AppendFormat(CultureInfo.CurrentCulture, "<span class=\"rem\">{0}</span>", line);
                }
                return sb.ToString();
            }
            if (match.Groups[3].Success) //asp tag
            {
                return string.Format(CultureInfo.CurrentCulture, "<span class=\"asp\">{0}</span>", match);
            }
            if (match.Groups[4].Success) //asp C# code
            {
                return csf.FormatSubCode(match.ToString());
            }
            if (match.Groups[5].Success) //tag delimiter
            {
                return string.Format(CultureInfo.CurrentCulture, "<span class=\"kwrd\">{0}</span>", match);
            }
            if (match.Groups[6].Success) //html tagname
            {
                return string.Format(CultureInfo.CurrentCulture, "<span class=\"html\">{0}</span>", match);
            }
            if (match.Groups[7].Success) //attributes
            {
                return attribRegex.Replace(match.ToString(), new MatchEvaluator(AttributeMatchEval));
            }
            return match.Groups[8].Success ? string.Format(CultureInfo.CurrentCulture, "<span class=\"attr\">{0}</span>", match) : match.ToString();
        }
        #endregion Operations
    }
}